02-19-24 (Monday)

Lord, you reveal your glory in every thing you made
Let us today engage with this creation so that we may participate in your beauty and wisdom
May the technology we use or even develop point us toward that,
Instead of dragging us towards our own vicious desires and illusions of freedom
Help us, and our society, to know your ways and be still, for yours is the glory
And our hearts are restless till they find rest in You.
Amen.

1 PyGame basics

What do I have to know?

1.1 Main game loop

  • Import pygame
  • Initialize
  • Set up the display screen
  • Create the main game loop, with usually the steps:
    • Catch events
    • Update game states
    • Draw everything
  • Quit the game in the event “QUIT”
import pygame

pygame.init() # Initialize pygame

screen = pygame.display.set_mode((800, 600)) # Set the screen
pygame.display.set_caption('My Pygame Window') # Set the window title

running = True
while running:
    # Check for events
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
            
    # Update game state

    # Render graphics

    pygame.display.flip() # Update the display

pygame.quit()

1.2 Drawing/Rendering

  • Example 1: fill a screen with white and draw a blue rectangle. More details on filling and forms.
import pygame

pygame.init() # Initialize pygame

screen = pygame.display.set_mode((800, 600)) # Set the screen
pygame.display.set_caption('My Pygame Window') # Set the window title

running = True
while running:
    # Check for events
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
    
    # Fill the screen with white
    screen.fill(WHITE)

    # Draw a red circle in the center of the screen
    pygame.draw.circle(screen, RED, (400, 300), 50)

    pygame.display.flip() # Update the display

pygame.quit()
  • Example 2: rendering sprites (get the image here)
    • Load the sprite before the game loop
    • Use “blit” to draw the sprite
import pygame

pygame.init() # Initialize pygame

# Set constants
WIDTH, HEIGHT = 800, 600
WHITE = (255, 255, 255)

screen = pygame.display.set_mode((800, 600)) # Set the screen
pygame.display.set_caption('My Pygame Window') # Set the window title

# Load the smiley face image
smiley_image = pygame.image.load("smiley.png")
smiley_rect = smiley_image.get_rect()
smiley_rect.center = (WIDTH // 2, HEIGHT // 2)

running = True
while running:
    # Check for events
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
    
    # Draw
    screen.fill(WHITE)
    screen.blit(smiley_image, smiley_rect)

    pygame.display.flip() # Update the display

pygame.quit()

1.3 Animating

  • Add parameters for center, radius and speed to make our smiley rotate
  • Update position according to each run of the game loop
  • Use clock.tick() to set a constant FPS rate at the game
import pygame
import math

pygame.init() # Initialize pygame

# Set constants
WIDTH, HEIGHT = 800, 600
WHITE = (255, 255, 255)

screen = pygame.display.set_mode((800, 600)) # Set the screen
pygame.display.set_caption('My Pygame Window') # Set the window title

# Load the smiley face image
smiley_image = pygame.image.load("smiley.png")
smiley_rect = smiley_image.get_rect()

# Animation parameters
radius = 200
angle = 0
speed = 0.02  # Controls the speed of the animation

clock = pygame.time.Clock() # start the clock
running = True
while running:
    # Check for events
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
    
    # Update state
    ### Update position
    x = WIDTH // 2 + int(math.cos(angle) * radius)
    y = HEIGHT // 2 + int(math.sin(angle) * radius)
    smiley_rect.center = (x, y)
    ### Update angle for next frame
    angle += speed
    
    # Draw
    screen.fill(WHITE)
    screen.blit(smiley_image, smiley_rect)

    pygame.display.flip() # Update the display
    clock.tick(60)

pygame.quit()

1.4 Catching events

  • You can catch more events such as mouse clicks or keyboard presses. For a list of event types, see here.
  • Let’s add the event of right and left arrow pressing to change the direction of the smiley
import pygame
import math

pygame.init() # Initialize pygame

# Set constants
WIDTH, HEIGHT = 800, 600
WHITE = (255, 255, 255)

screen = pygame.display.set_mode((800, 600)) # Set the screen
pygame.display.set_caption('My Pygame Window') # Set the window title

# Load the smiley face image
smiley_image = pygame.image.load("smiley.png")
smiley_rect = smiley_image.get_rect()

# Animation parameters
radius = 200
angle = 0
speed = 0.02  # Controls the speed of the animation

clock = pygame.time.Clock() # start the clock
running = True
while running:
    # Check for events
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_RIGHT:
                speed = 0.02
            elif event.key == pygame.K_LEFT:
                speed = -0.02
    
    # Update state
    ### Update position
    x = WIDTH // 2 + int(math.cos(angle) * radius)
    y = HEIGHT // 2 + int(math.sin(angle) * radius)
    smiley_rect.center = (x, y)
    ### Update angle for next frame
    angle += speed
    
    # Draw
    screen.fill(WHITE)
    screen.blit(smiley_image, smiley_rect)

    pygame.display.flip() # Update the display
    clock.tick(60)

pygame.quit()

1.5 Creating some classes

  • Let’s create a class “Player” to represent our main character: John Calvin.
    • Calvin
GRAVITY = 0.75 # setting a gravity constant

# Player class
class Player(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image = pygame.image.load("calvin_char.png")
        self.rect = self.image.get_rect()
        self.rect.center = (WIDTH // 2, HEIGHT // 2)
        self.velocity = pygame.math.Vector2(0, 0) # setting a vector velocity

    def update(self):
        # Apply gravity
        self.velocity.y += GRAVITY
        self.rect.y += self.velocity.y

        # Check collision with floor
        if self.rect.bottom > HEIGHT:
            self.rect.bottom = HEIGHT
            self.velocity.y = 0

    def jump(self):
        self.velocity.y = -12  # Jump strength
  • Now, we create the player and render in the screen.
    • You can use a sprite group to update and draw everything together! Just implement the function “update()” in all of them!
import pygame

# Initialize Pygame
pygame.init()

# Constants
WIDTH, HEIGHT = 800, 600
FPS = 60
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GRAVITY = 0.75

# Set up the game window
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Super Calvin Jump")

# Player class
class Player(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image = pygame.image.load("calvin_char.png")
        self.rect = self.image.get_rect()
        self.rect.center = (WIDTH // 2, HEIGHT // 2)
        self.velocity = pygame.math.Vector2(0, 0) # setting a vector velocity

    def update(self):
        # Apply gravity
        self.velocity.y += GRAVITY
        self.rect.y += self.velocity.y

        # Check collision with floor
        if self.rect.bottom > HEIGHT:
            self.rect.bottom = HEIGHT
            self.velocity.y = 0

    def jump(self):
        self.velocity.y = -12  # Jump strength

clock = pygame.time.Clock()
player = Player() # create Calvin!
all_sprites = pygame.sprite.Group(player) # add Calvin to the sprite group

# game loop
running = True
while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_SPACE:
                player.jump()

    # Update everything
    all_sprites.update()

    # Render everything
    screen.fill(WHITE)
    all_sprites.draw(screen)

    pygame.display.flip()
    clock.tick(FPS)
    
pygame.quit()
  • Let’s make it move left and right.
    • You can also check for events at the very time of the update. This is another way to implement some kind of logic.
MOVE_SPEED = 5

# Player class
class Player(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image = pygame.image.load("calvin_char.png")
        self.rect = self.image.get_rect()
        self.rect.center = (WIDTH // 2, HEIGHT // 2)
        self.velocity = pygame.math.Vector2(0, 0) # setting a vector velocity

    def update(self):
        # Apply gravity
        self.velocity.y += GRAVITY
        self.rect.y += self.velocity.y

        # Check collision with floor
        if self.rect.bottom > HEIGHT:
            self.rect.bottom = HEIGHT
            self.velocity.y = 0
            
        # Handle horizontal movement
        keys = pygame.key.get_pressed()
        if keys[pygame.K_LEFT]:
            self.velocity.x = -MOVE_SPEED
        elif keys[pygame.K_RIGHT]:
            self.velocity.x = MOVE_SPEED
        else:
            self.velocity.x = 0
            
        self.rect.x += self.velocity.x

    def jump(self):
        self.velocity.y = -12  # Jump strength

1.6 Collision detection

  • Let’s add some blocks to our scene.
    • We now need to check if Calvin is colliding with them!
class Block(pygame.sprite.Sprite):
    def __init__(self, x, y, width, height):
        super().__init__()
        self.image = pygame.Surface((width, height))
        self.image.fill(BLACK)
        self.rect = self.image.get_rect()
        self.rect.topleft = (x, y)
  • We can add them to a new group of blocks.
# Create blocks
blocks = pygame.sprite.Group()
blocks.add(Block(200, 400, 100, 20))
blocks.add(Block(400, 300, 100, 20))
blocks.add(Block(600, 200, 100, 20))

# Create player
player = Player()
all_sprites = pygame.sprite.Group(player)
  • And now, in the update function of Player, we check for collisions. We will use the function pygame.sprite.spritecollide.
    • We add the blocks group to the Player update function.
    def update(self, blocks):
        
        # update position
        self.rect.x += self.velocity.x
        self.rect.y += self.velocity.y
        
        # update velocities
        self.velocity.y += GRAVITY
        keys = pygame.key.get_pressed()
        if keys[pygame.K_LEFT]:
            self.velocity.x = -MOVE_SPEED
        elif keys[pygame.K_RIGHT]:
            self.velocity.x = MOVE_SPEED
        else:
            self.velocity.x = 0
        
        # Check collision with walls
        collisions = pygame.sprite.spritecollide(self, blocks, False)
        for block in collisions:
            clip_rect = self.rect.clip(block.rect)
            if clip_rect.width > clip_rect.height: # up or down
                if self.rect.bottom > block.rect.top and self.rect.bottom < block.rect.bottom:
                    self.rect.bottom = block.rect.top
                    self.velocity.y = 0
                elif self.rect.top < block.rect.bottom:
                    self.rect.top = block.rect.bottom
                    self.velocity.y = 0
            else: # right or left
                if self.rect.right > block.rect.left and self.rect.right < block.rect.right:
                    self.rect.right = block.rect.left
                    self.velocity.x = 0
                elif self.rect.left < block.rect.right:
                    self.rect.left = block.rect.right
                    self.velocity.x = 0
                
        # Check collision with floor
        if self.rect.bottom > HEIGHT:
            self.rect.bottom = HEIGHT
            self.velocity.y = 0
  • In this case, we will detect collision only in terms of rectangles. Later, you may need to implement a collision mask. See here.

1.7 Sound

  • Let’s add a jump sound. Out of the main loop, load the sound:
jump_sound = pygame.mixer.Sound('jump.mp3')
  • Now make it play when Calvin jumps!
    def jump(self):
        self.velocity.y = -12  # Jump strength
        jump_sound.play()

1.8 Multiple scenes

  • There are many ways to create multiple scenes, but a simple one is just create a function for each scene. For example:
def main_menu():
    
    bg = pygame.image.load("supercalvin.png")
    bg = pygame.transform.scale(bg, (800, 600))
    # game loop
    running = True
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
                pygame.quit()
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_SPACE:
                    running = False

        # Render everything
        screen.blit(bg, (0, 0))

        pygame.display.flip()
        clock.tick(FPS)
    
def first_level():
    # background
    bg = pygame.image.load("geneva.jpg")
    bg = pygame.transform.scale(bg, (800, 600))
    
    # game loop
    running = True
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_SPACE:
                    player.jump()

        # Update everything
        player.update(blocks)
        blocks.update()

        # Render everything
        screen.blit(bg, (0, 0))
        player_g.draw(screen)
        blocks.draw(screen)

        pygame.display.flip()
        clock.tick(FPS)

main_menu()
first_level()
pygame.quit()